#pragma once

#include <list>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <functional>

#include "Util.hpp"
#include "room_manage.hpp"

template <typename T>
class match_queue
{
    using wait_handle = std::function<bool()>;
public:

    size_t size() { return _queue.size(); }
    bool empty() { return _queue.empty(); }

    void push(const T& value)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _queue.push_back(value);
        _cond.notify_all();
    }

    bool pop(T& value)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!_queue.empty())
        {
            value = _queue.front();
            _queue.pop_front();
            return true;
        }

        return false;
    }

    void remove(const T& value)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _queue.remove(value);
    }

    void wait(wait_handle hdl = nullptr)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (hdl == nullptr)
            _cond.wait(lock);
        else 
            _cond.wait(lock, hdl);
    }

public:
    std::list<T> _queue;
    std::mutex _mutex;
    std::condition_variable _cond;
};

class mather
{
private:

    void handle_match(match_queue<uint32_t>& mqueue)
    {
        LOG(DEBUG, "handle_match() in");
        uint32_t uid1 = 0;
        uint32_t uid2 = 0;
        while (true)
        {
            mqueue.wait([&mqueue]() -> bool { return mqueue.size() >= 2; });

            // 获取两个uid
            if (!mqueue.pop(uid1))
                continue;
            if (!mqueue.pop(uid2))
            {
                mqueue.push(uid1);
                continue;
            }
            
            // 获取连接
            // 因为一次直接获取两个连接，若有一个连接掉线，需要将另一方的连接重新放回匹配队列中
            webserver_t::connection_ptr conn1 = _online_manager->get_hall_con_from_uid(uid1);
            if (conn1.get() == nullptr)
            {
                addMatch(uid2);
                continue;
            }
            webserver_t::connection_ptr conn2 = _online_manager->get_hall_con_from_uid(uid2);
            if (conn2.get() == nullptr)
            {
                addMatch(uid1);
                continue;
            }

            // 创建房间，若失败，重新添加匹配
            room_ptr rp = _room_manager->create_room(uid1, uid2);
            if (rp == nullptr)
            {
                addMatch(uid1);
                addMatch(uid2);
                continue;
            }

            LOG(DEBUG, "uid1: %d, uid2: %d", uid1, uid2);

            // 返回响应
            Json::Value resp;
            resp["optype"] = "match_success";
            resp["result"]  = true;

            std::string body;
            Util::json::serialize(resp, &body);

            conn1->send(body);
            conn2->send(body);
        }
    }

    void bronze_th_entry() { handle_match(_bronze_queue); }
    void gold_th_entry() { handle_match(_gold_queue); }
    void diamond_th_entry() { handle_match(_diamond_queue); }

public:
    mather(
        std::shared_ptr<room_manage> rm,
        std::shared_ptr<users_table> ut,
        std::shared_ptr<online_manager> om
    )
        : _room_manager(rm)
        , _user_table(ut)
        , _online_manager(om)
        , _bronze_th(std::bind(&mather::bronze_th_entry, this))
        , _gold_th(std::bind(&mather::gold_th_entry, this))
        , _diamond_th(std::bind(&mather::diamond_th_entry, this))
    {
        LOG(NORMAL, "匹配管理以初始化完毕");
    }

    bool addMatch(uint32_t uid)
    {
        Json::Value user;
        if (!_user_table->select_by_id(uid, user))
        {
            LOG(WARNING, " %d 玩家信息获取失败", uid);
            return false;
        }

        uint64_t score = user["current_score"].asUInt64();

        if (score < 2000)
        {
            _bronze_queue.push(uid);
        }
        else if (2000 <= score && score < 3000)
        {
            _gold_queue.push(uid);
        }
        else if (3000 <= score && score < 4000)
        {
            _diamond_queue.push(uid);
        }

        return true;
    }

    bool delMatch(uint32_t uid)
    {
        Json::Value user;
        if (!_user_table->select_by_id(uid, user))
        {
            LOG(WARNING, " %d 玩家信息获取失败", uid);
            return false;
        }

        uint64_t score = user["current_score"].asUInt64();

        if (score < 2000)
        {
            _bronze_queue.remove(uid);
        }
        else if (2000 <= score && score < 3000)
        {
            _gold_queue.remove(uid);
        }
        else if (3000 <= score && score < 4000)
        {
            _diamond_queue.remove(uid);
        }

        return true;
    }

private:
    match_queue<uint32_t> _bronze_queue;
    match_queue<uint32_t> _gold_queue;
    match_queue<uint32_t> _diamond_queue;

    std::thread _bronze_th;
    std::thread _gold_th;
    std::thread _diamond_th;

    std::shared_ptr<room_manage> _room_manager;
    std::shared_ptr<users_table> _user_table;
    std::shared_ptr<online_manager> _online_manager;
};